package com.sunday.common.http.apache.async;

import com.sunday.common.core.constants.LogConstants;
import com.sunday.common.core.gson.GsonUtils;
import com.sunday.common.http.apache.impl.DefaultConnectionKeepAliveStrategy;
import com.sunday.common.http.apache.sentinel.SentinelApacheHttpAsyncClientBuilder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

/**
 * 异步httpClient连接池构建器
 *
 * @author zsy
 * @since 2024/1/18
 */
@Slf4j
@Data
@AllArgsConstructor
public class AsyncHttpClientPoolBuilder {

    /**
     * httClient就会利用这个它。如果头部信息Keep-Alive在响应中不存在，HttpClient假设连接无限期的保持活动
     * 现在默认拒绝永久连接 60000
     */
    private final static long KEEP_ALIVE_MS = 60000;
    /**
     * 缺省: http连接池中connection的存活时间 （复用在空闲连接巡检上）
     */
    private final static TimeValue MAX_IDLE_TIME = TimeValue.ofMinutes(1);
    /**
     * 请求配置的对象
     */
    private RequestConfig requestConfig;
    /**
     * 连接管理器
     */
    private PoolingAsyncClientConnectionManager connectionManager;

    public static AsyncHttpClientPoolBuilder create() {
        return create(AsyncHttpClientPoolBuilder.RequestBuilder.create().requestConfig(), AsyncHttpClientPoolBuilder.ConnectionManagerBuilder.create().connectionManager());
    }

    public static AsyncHttpClientPoolBuilder create(RequestConfig requestConfig) {
        return create(requestConfig, AsyncHttpClientPoolBuilder.ConnectionManagerBuilder.create().connectionManager());
    }

    public static AsyncHttpClientPoolBuilder create(PoolingAsyncClientConnectionManager connectionManager) {
        return create(AsyncHttpClientPoolBuilder.RequestBuilder.create().requestConfig(), connectionManager);
    }

    public static AsyncHttpClientPoolBuilder create(RequestConfig requestConfig, PoolingAsyncClientConnectionManager connectionManager) {
        return new AsyncHttpClientPoolBuilder(requestConfig, connectionManager);
    }

    public CloseableHttpAsyncClient httpAsyncClient() {
        HttpAsyncClientBuilder httpAsyncClientBuilder = new SentinelApacheHttpAsyncClientBuilder()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                // 使这个Httpclient实例使用后台线程主动地从连接池中退出空闲连接.
                .evictExpiredConnections()
                .evictIdleConnections(MAX_IDLE_TIME);

        httpAsyncClientBuilder.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE);

        CloseableHttpAsyncClient closeableHttpAsyncClient = httpAsyncClientBuilder.build();
        closeableHttpAsyncClient.start();

        return closeableHttpAsyncClient;
    }

    /**
     * 连接管理器
     *
     * @author zsy
     * @since 2024/1/19
     */
    @Data
    @NoArgsConstructor
    public static class ConnectionManagerBuilder {
        /**
         * 每条路由的最大值
         * 每个服务（ip + port）的允许并发请求数量 route: {}-> http://127.0.0.1:14001] 路由策略为 ip:port 组成
         * 路由key是 ip和port
         * <p>
         * 特别注意下面两个值
         * 1.当请求到达 线程池最大数量时，会已队列的形式慢慢消费
         * 2.当每个服务到达上限，也会慢慢进行消费，这是一个双重锁
         */
        private int maxPerRoute = 1_00;
        //总计当前正在交互的http请求数量 [total kept alive: 1; route allocated: 1 of 1; total allocated: 2 of 2]
        //线程池总
        // 大小
        private int maxTotal = 1_00_00;

        //http连接池中connection的存活时间
        private TimeValue timeToLive = TimeValue.ofMinutes(5);

        public ConnectionManagerBuilder(TimeValue timeToLive) {
            this.timeToLive = timeToLive;
        }

        public static AsyncHttpClientPoolBuilder.ConnectionManagerBuilder create() {
            return new AsyncHttpClientPoolBuilder.ConnectionManagerBuilder();
        }

        public static AsyncHttpClientPoolBuilder.ConnectionManagerBuilder create(TimeValue timeToLive) {
            return new AsyncHttpClientPoolBuilder.ConnectionManagerBuilder(timeToLive);
        }

        public PoolingAsyncClientConnectionManager connectionManager() {
            PoolingAsyncClientConnectionManager connectionManager = new PoolingAsyncClientConnectionManager(socketFactoryRegistry(), null, timeToLive);
            connectionManager.setMaxTotal(maxTotal);
            connectionManager.setDefaultMaxPerRoute(maxPerRoute);
            return connectionManager;
        }

        public Registry socketFactoryRegistry() {
            return RegistryBuilder.<TlsStrategy>create()
                    .register(URIScheme.HTTP.getId(), DefaultClientTlsStrategy.getDefault())
                    .register(URIScheme.HTTPS.getId(), ClientTlsStrategyBuilder.create()
                            .setSslContext(sslContext())
                            .setHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                            .build())
                    .build();
        }

        public SSLContext sslContext() {
            SSLContext sslContext = null;
            try {
                sslContext = SSLContext.getInstance("TLS");
                X509TrustManager tm = new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                };
                sslContext.init(null, new TrustManager[]{tm}, null);
            } catch (Throwable e) {
                log.error(LogConstants.error_format, e.toString(), e);
            }
            return sslContext;
        }

        @Override
        public String toString() {
            return GsonUtils.DEFAULT.toJson(this);
        }

    }

    /**
     * 请求配置的对象
     *
     * @author zsy
     * @since 2024/1/19
     */
    @Data
    public static class RequestBuilder {
        /**
         * 连接建立时间，三次握手完成时间 10S
         */
        private Timeout connectTimeout = Timeout.ofSeconds(10);
        /**
         * httpClient使用连接池来管理连接，这个时间就是从连接池获取连接的超时时间，可以想象下数据库连接池 20S
         */
        private Timeout connectionRequestTimeout = Timeout.ofSeconds(20);
        /**
         * 数据传输过程中数据包之间间隔的最大时间 10S
         */
        private Timeout responseTimeout = Timeout.ofSeconds(10);


        public static RequestBuilder create() {
            return new RequestBuilder();
        }

        public RequestConfig requestConfig() {
            return RequestConfig.custom()
                    .setConnectTimeout(connectTimeout)
                    .setConnectionRequestTimeout(connectionRequestTimeout)
                    .setResponseTimeout(responseTimeout)
                    .build();
        }

        @Override
        public String toString() {
            return GsonUtils.DEFAULT.toJson(this);
        }

    }

}